using System;
using System.Collections;

namespace fuzzyKittenVNC
{
	public class FrameOverflowException : Exception {}; 
	/// <summary>
	/// Class for WebSocketFrame, like in 
	/// http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-03
	/// </summary>
	public class WebSocketFrame
	{
		// see http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-03#page-20
		protected enum FIELD_OFFSET {
			MORE 				= 0x0000,
			RSV1 				= 0x0002,
			RSV2 				= 0x0003,
			RSV3 				= 0x0004,
			OPCODE 				= 0x0005,
			RSV4 				= 0x0008,
			PAYLOAD_LEN 		= 0x0009,
			PAYLOAD_EXT_LEN		= 0x0010
		}
		
		protected enum FIELD_LENGTH {
			MORE				= 0x01,
			RSV1				= 0x01,
			RSV2 				= 0x01,
			RSV3				= 0x01,
			OPCODE				= 0x04,
			RSV4				= 0x01,
			PAYLOAD_LEN			= 0x07,
			PAYLOAD_EXT_LEN		= 0x10,
			PAYLOAD_EXT_LEN_LONG= 0x3f
		}
		
		
		public enum OPCODES {
			OPCODE_CONTINUE  	= (byte) 0x00,
			OPCODE_CONN_CLOSE 	= (byte) 0x01,
			OPCODE_PING			= (byte) 0x02,
			OPCODE_PONG			= (byte) 0x03,
			OPCODE_TEXT_FRAME	= (byte) 0x04,
			OPCODE_BINARY		= (byte) 0x05
		}
		
		public enum MORE {
			MORE_FINAL_FRAME 	= (byte) 0x00,
			MORE_MORE_FOLLOW 	= (byte) 0x80
		}
		
		public enum RESV {
			RESV_DEFAULT	 	= (byte) 0x00
		}	

		public OPCODES opcode;
		public bool moreFlag = false;
		public int payload_length = 0;
		public byte[] extension_data = null;
		public byte[] application_data = null;
		public byte[] frame;

		public WebSocketFrame () {}
		public WebSocketFrame (bool fragment) {
			if(fragment)
				this.moreFlag = fragment;
		}
		
		public byte[] toBytes() {
			long byteSize = this.getFrameSize();
			long extLength = 0; 
			long appLength = 0;
			int curPosition = 0;		
			if(application_data != null)
				appLength = application_data.LongLength;
			
			if(extension_data != null)
				extLength = extension_data.LongLength;
			long payloadLength = extLength+appLength;
			if(payloadLength<0)
				throw new FrameOverflowException();
			
			this.frame = new byte[byteSize];
			frame[0] = 0;
			if(this.moreFlag == true)
				frame[0] |=  (byte) MORE.MORE_MORE_FOLLOW;
			else 
				frame[0] |=  (byte) MORE.MORE_FINAL_FRAME;
			
			frame[0] |= (byte) RESV.RESV_DEFAULT;
			frame[0] |= (byte) this.opcode;
			
			if(payloadLength<126) { // length fits in 7 bit 
				frame[1] = Convert.ToByte(payloadLength);
				curPosition = 2;
			} else if(payloadLength<Int16.MaxValue) { // send 4 byte int
				frame[1] = 126;
				for(int i=0;i<2;i++)
					frame[i+1] = Convert.ToByte(payloadLength>>(24-i*8)&(byte) 0xff);
				curPosition = 4;
			} else { // Send 4 byte long
				frame[1] = 127;
				for(int i=0;i<4;i++)
					frame[i+1] = Convert.ToByte(payloadLength>>(24-i*8)&(byte) 0xff);
				for(int i=0;i<4;i++)
					frame[i+5] = Convert.ToByte(payloadLength>>(56-i*8)&(byte) 0xff);
				curPosition = 9;
			}
			Array.Copy(this.application_data,0,this.frame,curPosition,this.application_data.Length);

			return this.frame;
		}

		public long getFrameSize() {
			long framesize = 0;
			long dataSize = 0;
			if(this.application_data != null)
				dataSize += this.application_data.LongLength;
			
			if(this.extension_data != null)
				dataSize += this.extension_data.LongLength;
			
		
			framesize += (int) FIELD_OFFSET.PAYLOAD_LEN-1 + (int) FIELD_LENGTH.PAYLOAD_LEN;
			if(dataSize < 126) {
				framesize += dataSize;
			} else if(dataSize == 126) {
				framesize += dataSize + (int) FIELD_LENGTH.PAYLOAD_EXT_LEN;
			} else if(dataSize >= 127) {
				framesize += dataSize + (int) FIELD_LENGTH.PAYLOAD_EXT_LEN_LONG;
			} 


			return framesize;
		}
		
		protected long getExtensionSize() {
			return 0; // at this time, no extension is defined
		}
	}
}

